/*!	 distance.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2008 Chris Moore
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <ETL/stringf>

#include "distance.h"
#include "renddesc.h"
#include "general.h"
#include <synfig/localization.h>
#include <ctype.h>
#endif

using namespace std;
using namespace etl;
using namespace synfig;

#define POINTS_PER_INCH		  (72.0)
#define INCHES_PER_METER	  (39.3700787402)
#define POINTS_PER_METER	  (POINTS_PER_INCH*INCHES_PER_METER)
#define CENTIMETERS_PER_METER (100.0)
#define MILLIMETERS_PER_METER (1000.0)

#define METERS_PER_UNIT		  (rend_desc.get_physical_w()/abs(rend_desc.get_tl()[0]-rend_desc.get_br()[0]))

Distance::Distance(const synfig::String& str)
{
    (*this) = str;

}

Distance&
Distance::operator=(const synfig::String& str)
{
    int i(0);
    float val;
    int ret(strscanf(str, "%f%n", &val, &i));

    if (ret <= 0) {
        // Error
        synfig::error("Distance::Distance(): Bad value \"%s\"", str.c_str());
        return *this;
    } else {
        value_ = val;
    }

    synfig::String sys(str.begin() + i, str.end());

    if (sys.size()) {
        system_ = ident_system(sys);
    } else
        // this would throw BadSystem if system_ was not previously provided
    {
        sys = system_name(system_);
    }

    return *this;
}

synfig::String
Distance::get_string(int digits)const
{
    digits = min(9, max(0, digits));
    String fmt(strprintf("%%.%01dg", digits));
    String str(strprintf(fmt.c_str(), value_));
    return strprintf("%s%s", str.c_str(), system_name(system_).c_str());
}

void
Distance::convert(Distance::System target, const RendDesc& rend_desc)
{
    value_ = get(target, rend_desc);
    system_ = target;
}

Real
Distance::get(Distance::System target, const RendDesc& rend_desc)const
{
    if (target == SYSTEM_UNITS) {
        return units(rend_desc);
    }

    if (target == SYSTEM_PIXELS) {
        return units(rend_desc) * METERS_PER_UNIT * rend_desc.get_x_res();
    }

    return meters_to_system(meters(rend_desc), target);
}

Real
Distance::meters()const
{
    switch (system_) {
    case SYSTEM_INCHES:
        return value_ / INCHES_PER_METER;

    case SYSTEM_POINTS:
        return value_ / POINTS_PER_METER;

    case SYSTEM_METERS:
        return value_;

    case SYSTEM_CENTIMETERS:
        return value_ / CENTIMETERS_PER_METER;

    case SYSTEM_MILLIMETERS:
        return value_ / MILLIMETERS_PER_METER;

    default:
        throw BadSystem();
    }
}

Real
Distance::meters(const RendDesc& rend_desc)const
{
    if (system_ > SYSTEM_PIXELS) {
        return meters();
    }

    if (system_ == SYSTEM_UNITS) {
        return value_ * METERS_PER_UNIT;
    }

    if (system_ == SYSTEM_PIXELS) {
        return value_ / rend_desc.get_x_res();
    }

    throw BadSystem();
}

Real
Distance::units(const RendDesc& rend_desc)const
{
    if (system_ == SYSTEM_UNITS) {
        return value_;
    }

    Real ret;

    if (system_ > SYSTEM_PIXELS) {
        ret = meters();
    } else {
        ret = value_ / rend_desc.get_x_res();
    }

    return ret / METERS_PER_UNIT;
}

Real Distance::meters_to_system(Real x, System target_system)
{
    switch (target_system) {
    case SYSTEM_INCHES:
        return x * INCHES_PER_METER;

    case SYSTEM_POINTS:
        return x * POINTS_PER_METER;

    case SYSTEM_METERS:
        return x;

    case SYSTEM_CENTIMETERS:
        return x * CENTIMETERS_PER_METER;

    case SYSTEM_MILLIMETERS:
        return x * MILLIMETERS_PER_METER;

    default:
        throw BadSystem();
    }
}

Distance::System Distance::ident_system(const synfig::String& x)
{
    synfig::String str;

    // Make it all upper case, and remove white space
    for (unsigned int i = 0; i < x.size(); i++)if (x[i] != ' ' && x[i] != '\t') {
            str += toupper(x[i]);
        }

    // If it is plural, make it singular
    if (str[str.size() - 1] == 'S') {
        str = synfig::String(str.begin(), str.end() - 1);
    }

    if (str.empty() || str == "U" || str == "UNIT") {
        return SYSTEM_UNITS;
    }

    if (str == "PX" || str == "PIXEL") {
        return SYSTEM_PIXELS;
    }

    if (str == "PT" || str == "POINT") {
        return SYSTEM_POINTS;
    }

    if (str == "IN" || str == "\"" || str == "INCHE" || str == "INCH") {
        return SYSTEM_INCHES;
    }

    if (str == "M" || str == "METER") {
        return SYSTEM_METERS;
    }

    if (str == "CM" || str == "CENTIMETER") {
        return SYSTEM_CENTIMETERS;
    }

    if (str == "MM" || str == "MILLIMETER") {
        return SYSTEM_MILLIMETERS;
    }

    synfig::warning("Distance::ident_system(): Unknown distance system \"%s\"", x.c_str());

    return SYSTEM_UNITS;
}

synfig::String  Distance::system_name(Distance::System system)
{
    switch (system) {
    case SYSTEM_UNITS:
        return "u";

    case SYSTEM_PIXELS:
        return "px";

    case SYSTEM_POINTS:
        return "pt";

    case SYSTEM_INCHES:
        return "in";

    case SYSTEM_METERS:
        return "m";

    case SYSTEM_MILLIMETERS:
        return "mm";

    case SYSTEM_CENTIMETERS:
        return "cm";

    default:
        throw BadSystem();
    }

    return synfig::String();
}

synfig::String  Distance::system_local_name(Distance::System system)
{
    switch (system) {
    case SYSTEM_UNITS:
        return _("Units");

    case SYSTEM_PIXELS:
        return _("Pixels");

    case SYSTEM_POINTS:
        return _("Points");

    case SYSTEM_INCHES:
        return _("Inches");

    case SYSTEM_METERS:
        return _("Meters");

    case SYSTEM_MILLIMETERS:
        return _("Millimeters");

    case SYSTEM_CENTIMETERS:
        return _("Centimeters");

    default:
        throw BadSystem();
    }

    return synfig::String();
}